
Imports filesystem
Imports applicationInfoOutput
Imports errorHandle

Namespace errorHandle
    'got an disordered log in frontdoor, so use sequentialLock now
    'but why synclock Console.Error got a failure?
#Const sequentialLockSolution = True

    Public Enum displayMode
        console
        msgbox
    End Enum

    Public Enum errorType
        first                       'begin of errorType
        application                 'for information around application
        critical                    'for application error, cannot be handled, will cause stop or out of service
        exclamation                 'for application error, can be handled, may cause more serious issue
        information                 'for normal information
        system                      'for system error, network/file IO or other
        warning                     'for application error, can be handled, will not cause more serious issue
        user                        'for user input issue, such as in c/s model or b/s model, c/b sends wrong data type
        other                       'for other output
        last                        'last of errorType
    End Enum

    Public Module errorHandle
        Public Event writeLog(ByVal errType As errorType, ByVal errtypeChar As Char, ByVal msg As String)
        Public Const errorTypeCount As Int64 = errorType.last - errorType.first + 1
        Public ReadOnly defaultLogPath As String = combinepath(deployRoot, "log")
        Public Const errorTypeChar As String = "_aceiswuo_"
        Public ReadOnly errorTypeDefination() As String = {"_", "application", "critical", "exclamation", _
                                                           "information", "system", "warning", "user", "other", "_"}
        Public Const seperator As String = character.comma + character.blank
        Public Const fileNameSuffix As String = ".errorHandle" + normalSuffix
        Public Const criticalFilenameSuffix As String = ".errorHandle" + criticalSuffix
        'default position is deploys\log\ or project\log\
        Public ReadOnly defaultLogFilename As String = generateDefaultFilename(defaultLogPath)

        Public colorful As Boolean = True
#If sequentialLockSolution Then
        Public lock As sequentialLock = Nothing
#Else
        'Public mutex As New System.Threading.Mutex
#End If
        Public fileStream As autoflushFileStream
        Public mode As displayMode
        Public skipErrorType As String = ""
        Public flushtimes As Int64 = 16

        Public ReadOnly CRITICAL_ERROR_COUNTER As Int64
        Public ReadOnly EXCLAMATION_ERROR_COUNTER As Int64
        Public ReadOnly SYSTEM_ERROR_COUNTER As Int64
        Public ReadOnly OTHER_ERROR_COUNTER As Int64

        Public Sub flush()
            If Not fileStream Is Nothing Then
                fileStream.Flush()
            End If
        End Sub

        Public Const NOLOGFILEMARK As String = "___NOLOGFILE___"

        Public Sub initial(ByVal fileName As String, Optional ByVal suffix As String = fileNameSuffix)
            generateFileStream(fileStream, fileName, suffix, NOLOGFILEMARK)
        End Sub

        Public Sub initial(ByVal newMode As displayMode)
            mode = newMode
        End Sub

        Public Sub finalize()
            closestream(fileStream)
#If sequentialLockSolution Then
            GC.KeepAlive(lock)
#End If
        End Sub

        Sub New()
            If strlen(errorTypeChar) <> errorTypeCount Then
                writeConsoleErrorline("errorTypeChar length <> errorType count")
            End If
            If errorTypeDefination.Length() <> errorTypeCount Then
                writeConsoleErrorline("errorTypeDefination length <> errorType count")
            End If
            If Not isdebugmode() Then
                registerUnhandledException(AddressOf unhandledException)
            End If
#If CANNOTDETERMIND Then
            registerConsoleExtHandle(AddressOf consoleCancelKeyEventHandler)
#End If
#If sequentialLockSolution Then
            lock = New sequentialLock()
#End If
            'do not auto initialize counter, counters in errorHandle will use the same setting with application
            CRITICAL_ERROR_COUNTER = counter.registerCounter("CRITICAL_ERROR_COUNTER", 1, autoInitialize:=False)
            EXCLAMATION_ERROR_COUNTER = counter.registerCounter("EXCLAMATION_ERROR_COUNTER", 4, autoInitialize:=False)
            SYSTEM_ERROR_COUNTER = counter.registerCounter("SYSTEM_ERROR_COUNTER", 4, autoInitialize:=False)
            OTHER_ERROR_COUNTER = counter.registerCounter("OTHER_ERROR_COUNTER", 1, autoInitialize:=False)
        End Sub

        Friend Sub unhandledException(ByVal ex As Exception)
            finalize()
        End Sub

#If CANNOTDETERMIND Then
        Friend Sub consoleCancelKeyEventHandler(ByRef consoleExtHandleFinish As Boolean)
            finalize()
            consoleExtHandleFinish = consoleExtHandleFinish And True
        End Sub
#End If

        Friend Sub raiseEvent2(ByVal errType As errorType, ByVal errtypeChar As Char, ByVal msg As String)
            RaiseEvent writeLog(errType, errtypeChar, msg)
        End Sub
    End Module
End Namespace

Public Module _errorHandle
    Public Function defaultLogPath() As String
        Return errorHandle.defaultLogPath
    End Function

    Public Function defaultLogFilename() As String
        Return errorHandle.defaultLogFilename
    End Function

    Public Function validErrorType(ByVal errType As errorType) As Boolean
        Return errType > errorType.first AndAlso errType < errorType.last
    End Function

    Public Function patchupErrMsg(ByVal errmsg As String _
                                  , Optional ByVal errType As errorType = errorType.information _
                                  , Optional ByVal errTypeChar As Char = character.null _
                                  , Optional ByVal extraInfo As String = Nothing) As String
        assert(validErrorType(errType), "errType should not be first and last.")

        Dim prefix As String = Nothing
        If errType <= errorType.first OrElse errType >= errorType.last OrElse errType = errorType.other Then
            If errTypeChar = character.null Then
                prefix = character.x
            Else
                prefix = Char.ToLower(errTypeChar)
            End If
        Else
            prefix = Char.ToLower(errorTypeChar(errType))
        End If
        prefix += seperator
        prefix += shortTime()
        prefix += seperator
        errmsg = prefix + errmsg + extraInfo

        'keep one \r\n
        strrplc(errmsg, newline.incode(), character.newline)

        Return errmsg
    End Function

    Public Function breakupErrMsg(ByVal errmsg As String, ByRef errType As errorType, ByRef errTypeChar As Char _
                                  , ByRef time As String, ByRef msg As String) As Boolean
        Dim index As Int32
        Dim seperatorlen As Int64 = strlen(seperator)
        index = errmsg.IndexOf(seperator)
        If index <> npos Then
            errTypeChar = strleft(errmsg, index)
            errmsg = strmid(errmsg, index + seperatorlen)
            Dim i As Int32
            For i = errorType.first + 1 To errorType.last - 1
                If errorTypeChar(i) = errTypeChar Then
                    errType = i
                    Exit For
                End If
            Next
            If i = errorType.last Then
                errType = errorType.other
            End If
        Else
            Return False
        End If

        index = errmsg.IndexOf(seperator)
        If index <> npos Then
            time = strleft(errmsg, index)
            errmsg = strmid(errmsg, index + seperatorlen)
        Else
            Return False
        End If

        msg = errmsg

        Return True
    End Function

    'the errmsg needs some time to create, so should try to avoid to use this way.
    <Runtime.CompilerServices.MethodImpl(Runtime.CompilerServices.MethodImplOptions.NoInlining)> _
    Public Sub debugRaiseError(ByVal errMsg As String _
                               , Optional ByVal errType As errorType = errorType.information _
                               , Optional ByVal errTypeChar As Char = character.null _
                               , Optional ByVal autologfile As Boolean = False _
                               , Optional ByVal additionaljump As Int32 = 0)
        If isdebugmode() Then
            raiseError(errMsg, errType, errTypeChar, autologfile, additionaljump + 1)
        End If
    End Sub

    <Runtime.CompilerServices.MethodImpl(Runtime.CompilerServices.MethodImplOptions.NoInlining)> _
    Public Sub debugRaiseError(ByVal mode As displayMode, ByVal errmsg As String _
                               , Optional ByVal errtype As errorType = errorType.information _
                               , Optional ByVal errtypeChar As Char = character.null _
                               , Optional ByVal autologfile As Boolean = False _
                               , Optional ByVal additionaljump As Int32 = 0)
        If isdebugmode() Then
            raiseError(mode, errmsg, errtype, errtypeChar, autologfile, additionaljump + 1)
        End If
    End Sub

    <Runtime.CompilerServices.MethodImpl(Runtime.CompilerServices.MethodImplOptions.NoInlining)> _
    Public Sub raiseError(ByVal errMsg As String _
                          , Optional ByVal errType As errorType = errorType.information _
                          , Optional ByVal errTypeChar As Char = character.null _
                          , Optional ByVal autologfile As Boolean = False _
                          , Optional ByVal additionaljump As Int32 = 0)
        raiseError(mode, errMsg, errType, errTypeChar, autologfile, additionaljump + 1)
    End Sub

    <Runtime.CompilerServices.MethodImpl(Runtime.CompilerServices.MethodImplOptions.NoInlining)> _
    Public Sub raiseError(ByVal errType As errorType, ByVal errTypeChar As Char, ByVal additionaljump As Int32, _
                          ByVal ParamArray os() As Object)
        Dim s As String = Nothing
        If Not os Is Nothing AndAlso os.Length() > 0 Then
            For i As Int32 = 0 To os.Length() - 1
                s += Convert.ToString(os(i))
            Next
        End If
        raiseError(s, errType, errTypeChar, additionaljump:=additionaljump + 1)
    End Sub

    <Runtime.CompilerServices.MethodImpl(Runtime.CompilerServices.MethodImplOptions.NoInlining)> _
    Public Sub raiseError(ByVal errType As errorType, ByVal errTypeChar As Char, ByVal ParamArray os() As Object)
        raiseError(errType, errTypeChar, 1, os)
    End Sub

    <Runtime.CompilerServices.MethodImpl(Runtime.CompilerServices.MethodImplOptions.NoInlining)> _
    Public Sub raiseError(ByVal errType As errorType, ByVal ParamArray os() As Object)
        raiseError(errType, character.null, 1, os)
    End Sub

    <Runtime.CompilerServices.MethodImpl(Runtime.CompilerServices.MethodImplOptions.NoInlining)> _
    Public Sub raiseError(ByVal ParamArray os() As Object)
        raiseError(errorType.information, character.null, 1, os)
    End Sub

    <Runtime.CompilerServices.MethodImpl(Runtime.CompilerServices.MethodImplOptions.NoInlining)> _
    Public Sub raiseError(ByVal mode As displayMode, ByVal errmsg As String _
                        , Optional ByVal errtype As errorType = errorType.information _
                        , Optional ByVal errtypeChar As Char = character.null _
                        , Optional ByVal autologfile As Boolean = False, Optional ByVal additionaljump As Int32 = 0)
        errmsg = patchupErrMsg(errmsg, errtype, errtypeChar, seperator + character.at + callingcode(additionaljump + 1))

#If sequentialLockSolution Then
#If DEBUG Then
        Dim tokengot As Int32
        lock.wait(tokengot)
#Else
        lock.wait()
#End If
#Else
        'If mutex.WaitOne() Then
        SyncLock Console.Error
#End If
        If skipErrorType.IndexOf(errorTypeChar(errtype).ToString) = -1 AndAlso _
           skipErrorType.IndexOf(errtypeChar) = -1 Then
            If mode = displayMode.console Then
#If Not (PocketPC OrElse Smartphone) Then
                Dim oldcolor As ConsoleColor = Nothing
                If colorful Then
                    oldcolor = Console.ForegroundColor
                    If errtype = errorType.application Then
                        Console.ForegroundColor = ConsoleColor.Blue
                    ElseIf errtype = errorType.critical Then
                        Console.ForegroundColor = ConsoleColor.Red
                        counter.increase(CRITICAL_ERROR_COUNTER)
                    ElseIf errtype = errorType.exclamation Then
                        Console.ForegroundColor = ConsoleColor.Yellow
                        counter.increase(EXCLAMATION_ERROR_COUNTER)
                    ElseIf errtype = errorType.information Then
                        Console.ForegroundColor = ConsoleColor.White
                    ElseIf errtype = errorType.other Then
                        Console.ForegroundColor = ConsoleColor.Magenta
                        counter.increase(OTHER_ERROR_COUNTER)
                    ElseIf errtype = errorType.user Then
                        Console.ForegroundColor = ConsoleColor.Cyan
                    ElseIf errtype = errorType.system Then
                        Console.ForegroundColor = ConsoleColor.Gray
                        counter.increase(SYSTEM_ERROR_COUNTER)
                    Else
                        Console.ForegroundColor = ConsoleColor.Green
                    End If
                End If
#End If
                Console.Error.WriteLine(errmsg)
#If Not (PocketPC OrElse Smartphone) Then
                If colorful Then
                    Console.ForegroundColor = oldcolor
                End If
#End If
            ElseIf mode = displayMode.msgbox Then
                MsgBox(errmsg)
            End If
        End If

        If fileStream Is Nothing AndAlso autologfile Then
            initial(defaultLogFilename())
        End If
        If Not fileStream Is Nothing Then
            If Not fileStream.CanWrite Then
                initial(defaultLogFilename(), criticalFilenameSuffix)
            End If
            If fileStream.CanWrite Then
                Dim data() As Byte
                data = encoding.utf8.GetBytes(errmsg + newline.incode())
                fileStream.Write(data, 0, data.Length)
                Static writetimes As Int64 = 0
                writetimes += 1
                If writetimes = maxInt64 Then
                    writetimes = 0
                End If
                If flushtimes > 0 AndAlso writetimes Mod flushtimes = 0 Then
                    fileStream.Flush()
                End If
            End If
        End If
        raiseEvent2(errtype, errtypeChar, errmsg)

#If sequentialLockSolution Then
#If DEBUG Then
        lock.release(tokengot)
#Else
        lock.release()
#End If
#Else
        End SyncLock
        'mutex.ReleaseMutex()
        'End If
#End If
    End Sub
End Module
